package cn.zyq.charging.order.rabbitmq;

import cn.zyq.charging.order.constant.OrderStatusConstant;
import cn.zyq.charging.order.dao.mapper.OrderSuccessMapper;
import cn.zyq.charging.order.pojo.po.ChargingBillSuccessPO;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import com.rabbitmq.client.Channel;
import cn.zyq.charging.order.pojo.po.OrderMQPO;
import org.springframework.amqp.core.Message;
import org.springframework.stereotype.Component;
import cn.zyq.charging.order.config.RabbitMQConfiguration;
import cn.zyq.charging.order.dao.repository.OrderRepository;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;

/**
 * rabbit-mq消费者的业务实现代码
 * 消费的是死信队列
 * 用死信队列处理充电超时订单的逻辑：
 *   1. 当充电订单超过设定的时间后，会将正常队列的消息转入到死信队列。
 *   2. 然后死信队列的消费端会处理死信队列的每一条消息，死信队列的消息消费者获取一条消息后，判断如果此订单是正在充电的订单的话就将此订单状态改为异常结束。
 */
@Slf4j
@Component
public class RabbitMQOrderConsumer_ForDeadLetterQueue {

    @Autowired(required = false)
    private OrderRepository orderRepository;

    /**
     * @RabbitListener 类似于 我们写的回调类 RabbitMQTests#subscribe DeliverCallback
     * 只需添加 @RabbitListener 注解 加上我们要监听的队列(死信队列)
     * 当死信队列里有消息的时候,就会调用本方法
     * @param orderMQPO
     * @param message   要消费的消息
     * @param channel  管道
     */
    @RabbitListener(queues = RabbitMQConfiguration.Order_DEAD_LETTER_QUEUE_NAME_DIRECT)
    public void consumerOrderDeadLetter(OrderMQPO orderMQPO, Message message, Channel channel) throws IOException {
        log.debug("消费死信队列消息:{}",orderMQPO);
        //如何保证消息不丢失?
        // 1.生产者发的消息不丢失: 可以通过confirm机制(生产者发送消息后RabbitMQ中间件必须响应) ,
        // 来确认RabbitMQ中间件已经收到生产者发送的消息
        // 2.RabbitMQ本身不丢:   queue和exchange都需要开启持久化(durable为true)
        // 3.消息者 消费消息不丢:  手动ack
        // 注：消息队列中的ack是什么意思?
        // 消息者 拿到消息 处理业务 更新订单状态
        // 正常情况:  更新订单状态成功 返回ack给消息队列,消息队列收到ack后,认为消费者消费成功,
        // 会把这个条消息从消息队列删除,消费者继续消费下一条消息
        // 异常情况： 数据库出现异常,更新失败,不返回ack给消息队列
        // 消息队列没有收到ack,认为消费者消费失败,不会删除这条消息,会一直保存,
        // 等数据库修好了,继续重复消费这条消息,数据没丢,继续更新订单的状态



        ChargingBillSuccessPO chargingBillSuccessPO = new ChargingBillSuccessPO();
        chargingBillSuccessPO.setBillStatus(OrderStatusConstant.ORDER_STATUS_EXCEPTION_END);


        //把正在充电中的订单数据的状态修改为 异常结束
        //update charging_bill_success set bill_status = 3 where bill_id = orderNo and bill_status = 1
        //2 to two



        /*UpdateWrapper<ChargingBillSuccessPO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("bill_id",orderMQPO.getOrderNo());
        updateWrapper.eq("bill_status",OrderStatusConstant.ORDER_STATUS_PROGRESS);
        //chargingBillSuccessPO里边封装了要修改的新数据， updateWrapper里边封装了update语句的条件。
        orderSuccessMapper.update(chargingBillSuccessPO,updateWrapper);*/
        //后边会改进：将上边4行代码会封装到orderRepository的updateOrderStatusFromProcess2ExceptionEnd(orderMQPO.getOrderNo())方法
        //因为耦合性太强了(假设后期出现一个更好用的mybatis框架，则需要修改这里的硬编码，如果换成注入的方式则可以直接更换注入的实现类对象即可)

        orderRepository.updateOrderStatusFromProcess2ExceptionEnd(orderMQPO.getOrderNo());


        //注意，测试流程：
        //1.先用 doc.html网页提交数据，提交成功后。
        //2.再到 rabbitmq客户端(模拟充电设备)给RabbitMQ服务端 start_success充电成功的消息。
        //3.如果此消息没有消费端，则会在2分钟后会从RabbitMQ的正常队列转到死信队列。
        //4.本方法还会将订单状态改为异常结束。

        //手动ack
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //参数1：long deliveryTag 消息的唯一标识。每条消息都有自己的ID号，用于标识该消息在channel中的顺序。
        //  当消费者接收到消息后，需要调用channel.basicAck方法并传递deliveryTag来确认消息的处理。
        //参数2：boolean multiple 是否批量确认消息，当传false时，只确认当前 deliveryTag对应的消息;
        //  当传true时，会确认当前及之前所有未确认的消息。
        channel.basicAck(deliveryTag,false);
    }


    @Autowired
    private OrderSuccessMapper orderSuccessMapper;
}






